﻿#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QMessageBox>
#include <QFileDialog>
#include <QDesktopServices>

const int g_bSzieMax = 1024;
const int g_kSizeMax = 1024 * 1024;
const int g_mSizeMax = 1024 * 1024 * 1024;
const long long g_gSizeMax = 1024 * 1024 * 1024 * 1024;

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle(u8"文件下载器");
    setFixedSize(700, 300);

    manager = new QNetworkAccessManager(this);

    mtimer = new QTimer();
    connect(mtimer, SIGNAL(timeout()), this, SLOT(showSpeed()));

//    http://5b0988e595225.cdn.sohucs.com/images/20180516/3fea28f2c55c4959a87e2f84487eb08e.jpeg
//    ui->urlLineEdit->setText("http://xz5.jb51.net:81/201604/books/cprimer5_jb51.rar");
//    ui->downloadPath->setText("F:/C++/QT/project/xxxx");
}

MainWindow::~MainWindow()
{
    delete ui;
}

// 网络响应结束
void MainWindow::on_finished()
{
    QFileInfo fileInfo;
    fileInfo.setFile(downloadedFile->fileName());

    downloadedFile->close();
    delete downloadedFile;
    downloadedFile = Q_NULLPTR;

    reply->deleteLater();
    reply = Q_NULLPTR;

    // 停止计时
    mtimer->stop();

    ui->downLoad->setEnabled(true);

    //打开下载的文件
   if (ui->checkBox->isChecked())
   {
       QDesktopServices::openUrl(QUrl::fromLocalFile(ui->downloadPath->text().trimmed()));
   }

}

// 读取缓冲区下载的数据
void MainWindow::on_readyRead()
{
    downloadedFile->write(reply->readAll());
}

/**
 * @brief MainWindow::on_downloadProgress
 * @param bytesRead   已下载大小
 * @param totalBytes  文件大小
 */
void MainWindow::on_downloadProgress(qint64 bytesRead, qint64 totalBytes)
{
    ui->progressBar->setMaximum(totalBytes);
    ui->progressBar->setValue(bytesRead);

    hadLoadSize = bytesRead;
    QString size = formatSize(hadLoadSize);
    ui->downloaded->setText(size);
}

// 浏览设置下载路径
void MainWindow::on_choosePath_clicked()
{
    QString filePath = QFileDialog::getExistingDirectory(this, QString::fromLocal8Bit("选择文件路径"), ".");
    ui->downloadPath->setText(filePath);
}

void MainWindow::on_downLoad_clicked()
{
    QString url = ui->urlLineEdit->text().trimmed();
    qDebug() << url;
    downLoadUrl = url;

    if (url.isEmpty())
    {
        QMessageBox::information(this, u8"错误",
                                 u8"请指定需要下载的URL", NULL);
        return;
    }

    QUrl tmpUrl = QUrl::fromUserInput(url);
    if (!tmpUrl.isValid())
    {
        QMessageBox::information(this, u8"错误",
                    u8"无效URL: " + url + u8"\n错误信息: " + tmpUrl.errorString(), NULL);
        return;
    }

    showFileSize();

    QString dirPath = ui->downloadPath->text().trimmed();
    if (dirPath.isEmpty())
    {
        QMessageBox::information(this, u8"错误",
                                 u8"请设置下载目录", NULL);
        return;
    }

    QString newFileName = ui->fileName->text().trimmed();
    QString fullFileName;
    if (newFileName.isEmpty())
    {
        fullFileName = dirPath + "/" + tmpUrl.fileName();
    }
    else {
        fullFileName = dirPath + "/" + newFileName;
    }

    if (QFile::exists(fullFileName))
    {
        QFile::remove(fullFileName);
    }

    downloadedFile = new QFile(fullFileName);
    if (!downloadedFile->open(QIODevice::WriteOnly))
    {
        QMessageBox::information(this, u8"错误",
                                 u8"临时文件打开错误", NULL);
        return;
    }

    ui->downLoad->setEnabled(false);

    // 500ms
    mtimer->start(100);
    startTime = QTime::currentTime();

    reply = manager->get(QNetworkRequest(tmpUrl));

    // 下载完响应finished信号
    connect(reply, SIGNAL(finished()), this, SLOT(on_finished()));
    // 缓冲区有新的下载数据时，响应readyRead信号
    connect(reply, SIGNAL(readyRead()), this, SLOT(on_readyRead()));
    // 下载进度控制
    connect(reply, SIGNAL(downloadProgress(qint64, qint64)),
            this, SLOT(on_downloadProgress(qint64, qint64)));

}

/**
 * @brief MainWindow::getFileTotalSize  发送请求，获取头部信息
 * @param url
 * @return
 */
qint64 MainWindow::getFileTotalSize(QString url)
{
    qint64 size = -1;
    // 请求的次数,最多3次尝试
    int tryTimes = 3;

    do
    {
        QNetworkAccessManager manager;
        // 事件循环，等待请求文件头信息结束;
        QEventLoop loop;
        // 超时，结束事件循环;
        QTimer timer;

        QNetworkReply *reply = manager.head(QNetworkRequest(url));
        if (!reply)
        {
            continue;
        }

        connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
        connect(&timer,SIGNAL(timeout()), &loop, SLOT(quit()));

        // 超过2s结束事件循环，重新请求
        timer.start(2000);
        loop.exec();

        if (reply->error() != QNetworkReply::NoError)
        {
            // 请求发生错误
            qDebug() << reply->errorString();
            continue;
        }
        else if (!timer.isActive())
        {
            // 请求超时，未获取到文件信息
            qDebug() << "Request Timeout";
            continue;
        }
        timer.stop();

        QVariant var = reply->header(QNetworkRequest::ContentLengthHeader);
        size = var.toLongLong();
        reply->deleteLater();
        break;

    }while (tryTimes--);

    return size;
}

/**
 * @brief MainWindow::doubleToQString double转字符串并保留3位小数
 * @param dbNum
 * @return
 */
QString MainWindow::doubleToQString(const double &dbNum)
{
    char *chCode;
    chCode = new(std::nothrow)char[20];
    // .3 是控制输出精度的，两位小数
    sprintf(chCode, "%.3lf", dbNum);
    QString strCode(chCode);
    delete []chCode;
    return strCode;
}

/**
 * @brief MainWindow::formatSize  根据size大小转换显示格式
 * @param size
 * @return
 */
QString MainWindow::formatSize(qint64 size)
{
    QString sizeStr;

    if (size <= g_bSzieMax)
    {
        sizeStr = doubleToQString(size) + " B";
    }
    else if ((size > g_bSzieMax) && (size <= g_kSizeMax))
    {
        sizeStr = doubleToQString(double(size)/g_bSzieMax) + " KB";
    }
    else if ((size > g_kSizeMax) && (size <= g_mSizeMax))
    {
        sizeStr = doubleToQString(double(size)/g_kSizeMax) + " MB";
    }
    else if ((size > g_mSizeMax) && (size <= g_gSizeMax))
    {
        sizeStr = doubleToQString(double(size)/g_mSizeMax) + " GB";
    }

    return sizeStr;
}

//显示文件大小
void MainWindow::showFileSize()
{
    qint64 size = getFileTotalSize(downLoadUrl);

    QString sizeStr = formatSize(size);

    ui->fileSize->setText(sizeStr);
}


void MainWindow::on_urlLineEdit_textChanged(const QString &arg1)
{
    QUrl url = arg1;
    QFileInfo info(url.path());
    QString fileName(info.fileName());

    ui->fileName->setText(fileName);
}

//显示速度
void MainWindow::showSpeed()
{
    stopTime = QTime::currentTime();
    // 已下载大小/(时间差(ms)/1000) --> byte/s
    double speed = double(hadLoadSize) / startTime.msecsTo(stopTime) * 1000;
    QString speedStr;

    if (speed <= g_bSzieMax)
    {
        speedStr = doubleToQString(speed) + " B/s";
    }
    else if ((speed > g_bSzieMax) && (speed <= g_kSizeMax))
    {
        speedStr = doubleToQString(speed/g_bSzieMax) + " KB/s";
    }
    else if ((speed > g_kSizeMax) && (speed <= g_mSizeMax))
    {
        speedStr = doubleToQString(speed/g_kSizeMax) + " MB/s";
    }
    else if ((speed > g_mSizeMax) && (speed <= g_gSizeMax))
    {
        speedStr = doubleToQString(speed/g_mSizeMax) + " GB/s";
    }

    ui->speed->setText(speedStr);
}
